#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import sys
import os
import time
import threading
import traceback
import serial.tools.list_ports

# 先设置环境变量，然后再导入Qt模块
try:
    import site
    # 获取可能的路径
    site_packages = site.getsitepackages()[0]
    venv_site_packages = os.path.join(sys.prefix, 'Lib', 'site-packages')
    
    # 检查几个可能的插件位置
    plugin_paths = [
        os.path.join(venv_site_packages, 'PyQt5', 'Qt5', 'plugins'),
        os.path.join(venv_site_packages, 'PyQt5', 'Qt', 'plugins'),
        os.path.join(site_packages, 'PyQt5', 'Qt5', 'plugins'),
        os.path.join(site_packages, 'PyQt5', 'Qt', 'plugins'),
        os.path.join(sys.prefix, 'Library', 'plugins'),
    ]
    
    # 查找存在的路径
    for path in plugin_paths:
        if os.path.exists(path):
            os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = path
            print(f"设置 QT_QPA_PLATFORM_PLUGIN_PATH = {path}")
            
            # 同时设置 PATH
            if sys.platform.startswith('win'):
                bin_path = os.path.join(os.path.dirname(path), 'bin')
                if os.path.exists(bin_path):
                    os.environ['PATH'] = bin_path + os.pathsep + os.environ.get('PATH', '')
                    print(f"添加到 PATH: {bin_path}")
            break
    else:
        print("警告: 未找到 Qt 插件目录!")
except Exception as e:
    print(f"设置环境变量时出错: {e}")
    traceback.print_exc()

# 现在导入Qt模块
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, 
                            QHBoxLayout, QGridLayout, QLabel, QLineEdit, 
                            QComboBox, QSpinBox, QDoubleSpinBox, QPushButton, 
                            QCheckBox, QTextEdit, QGroupBox, QMessageBox,
                            QTabWidget, QFrame, QSplitter, QDesktopWidget,
                            QStackedWidget)
from PyQt5.QtCore import Qt, QTimer, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QFont, QColor, QTextCharFormat, QTextCursor, QIcon

from plc_clients import get_plc_client, get_supported_devices
from plc_clients.factory import validate_client_parameters

class PLCControlUI(QMainWindow):
    """PLC多品牌控制面板 - PyQt5版本"""
    
    # 自定义信号
    log_signal = pyqtSignal(str, str)  # 参数：消息、类型(info, error, success)
    display_signal = pyqtSignal(str)   # 参数：显示文本
    
    def __init__(self):
        super().__init__()
        
        # 设置窗口属性
        self.setWindowTitle("华荣股份PLC调试助手")
        
        # 设置图标
        try:
            # 使用os.path.abspath确保在不同环境下路径正确
            logo_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "logo.png")
            if os.path.exists(logo_path):
                self.setWindowIcon(QIcon(logo_path))
        except Exception as e:
            print(f"加载图标失败: {e}")
            
        self.setGeometry(100, 100, 900, 700)
        
        # PLC通信相关
        self.plc = None
        self.device_type = None  # 当前设备类型
        self.connected = False
        self.monitor_timer = QTimer(self)
        self.monitor_timer.timeout.connect(self.poll_plc)
        
        # 创建UI
        self.create_ui()
        
        # 创建菜单栏
        self.create_menubar()
        
        # 初始化
        self.update_ui_elements()
        
    def create_ui(self):
        """创建用户界面"""
        central_widget = QWidget()
        main_layout = QVBoxLayout(central_widget)
        self.setCentralWidget(central_widget)
        
        # 通信设置
        comm_group = self.create_comm_group()
        main_layout.addWidget(comm_group)
        
        # 分割线
        line = QFrame()
        line.setFrameShape(QFrame.HLine)
        line.setFrameShadow(QFrame.Sunken)
        main_layout.addWidget(line)
        
        # 中间部分：控制区和监控区
        mid_widget = QSplitter(Qt.Horizontal)
        
        # 左侧：控制面板
        control_widget = QTabWidget()
        
        # 创建读写面板
        read_write_tab = self.create_read_write_tab()
        control_widget.addTab(read_write_tab, "读写操作")
        
        # 多项监控面板（可以后续扩展）
        # monitor_tab = self.create_monitor_tab()
        # control_widget.addTab(monitor_tab, "批量监控")
        
        # 右侧：结果显示和日志
        right_widget = QWidget()
        right_layout = QVBoxLayout(right_widget)
        
        # 监控结果
        monitor_group = QGroupBox("读取结果")
        monitor_layout = QVBoxLayout(monitor_group)
        self.result_text = QTextEdit()
        self.result_text.setReadOnly(True)
        monitor_layout.addWidget(self.result_text)
        right_layout.addWidget(monitor_group)
        
        # 日志
        log_group = QGroupBox("操作日志")
        log_layout = QVBoxLayout(log_group)
        self.log_text = QTextEdit()
        self.log_text.setReadOnly(True)
        log_layout.addWidget(self.log_text)
        right_layout.addWidget(log_group)
        
        # 添加到分割器
        mid_widget.addWidget(control_widget)
        mid_widget.addWidget(right_widget)
        mid_widget.setStretchFactor(0, 1)
        mid_widget.setStretchFactor(1, 1)
        
        main_layout.addWidget(mid_widget)
        
        # 状态栏
        self.statusBar().showMessage("就绪")
        
        # 添加设备状态显示区域
        self.create_device_status_area()
        
        # 初始化设备相关的UI元素，确保所有控件都已创建
        self.on_device_type_changed(0)
        
        # 连接信号
        self.log_signal.connect(self.update_log)
        self.display_signal.connect(self.update_display)

    def load_supported_devices(self):
        """动态加载支持的设备类型"""
        try:
            devices = get_supported_devices()
            self.device_mapping = {}  # 存储显示名称到设备类型的映射
            
            for device_type, info in devices.items():
                display_name = f"{info['manufacturer']} - {info['communication']}"
                self.device_type_combo.addItem(display_name, device_type)
                self.device_mapping[display_name] = device_type
                
            self.log_signal.emit(f"已加载 {len(devices)} 种设备类型", "info")
        except Exception as e:
            # 如果动态加载失败，使用默认设备列表
            self.device_type_combo.addItems([
                "三菱 FX3U (TCP)", 
                "三菱 FX3U (串口)", 
                "西门子 S7 (TCP)"
            ])
            self.device_mapping = {
                "三菱 FX3U (TCP)": "mitsubishi_fx3u_tcp",
                "三菱 FX3U (串口)": "mitsubishi_fx3u_rtu", 
                "西门子 S7 (TCP)": "siemens_s7_tcp"
            }
            self.log_signal.emit(f"使用默认设备列表: {str(e)}", "warning")

    def create_comm_group(self):
        """创建通信设置区域"""
        group = QGroupBox("通信设置")
        main_layout = QVBoxLayout(group)
        
        # 设备类型选择
        device_type_layout = QHBoxLayout()
        device_type_layout.addWidget(QLabel("设备类型:"))
        self.device_type_combo = QComboBox()
        
        # 动态加载支持的设备类型
        self.load_supported_devices()
        
        self.device_type_combo.currentIndexChanged.connect(self.on_device_type_changed)
        device_type_layout.addWidget(self.device_type_combo)
        device_type_layout.addStretch(1)
        main_layout.addLayout(device_type_layout)
        
        # 创建参数设置区域的堆叠小部件
        self.params_stack = QStackedWidget()
        
        # 串口参数页面
        serial_widget = QWidget()
        serial_layout = QHBoxLayout(serial_widget)
        
        # COM端口
        serial_layout.addWidget(QLabel("COM端口:"))
        self.com_port_combo = QComboBox()
        self.com_port_combo.setMinimumWidth(150) # 适当调整最小宽度
        self.refresh_serial_ports()
        serial_layout.addWidget(self.com_port_combo)
        self.refresh_btn = QPushButton("刷新")
        self.refresh_btn.clicked.connect(self.refresh_serial_ports)
        self.refresh_btn.setMaximumWidth(60)
        serial_layout.addWidget(self.refresh_btn)
        
        serial_layout.addStretch(1) # 分隔
        
        # 波特率
        serial_layout.addWidget(QLabel("波特率:"))
        self.baudrate_combo = QComboBox()
        self.baudrate_combo.addItems(["9600", "19200", "38400", "57600", "115200"])
        self.baudrate_combo.setCurrentText("9600")
        self.baudrate_combo.setMinimumWidth(80)
        serial_layout.addWidget(self.baudrate_combo)
        
        serial_layout.addStretch(1) # 分隔
        
        # 校验位
        serial_layout.addWidget(QLabel("校验位:"))
        self.parity_combo = QComboBox()
        self.parity_combo.addItems(["N", "E", "O"])
        self.parity_combo.setCurrentText("N")
        serial_layout.addWidget(self.parity_combo)
        
        serial_layout.addStretch(1) # 分隔
        
        # 从站地址
        serial_layout.addWidget(QLabel("从站地址:"))
        self.serial_slave_id_spin = QSpinBox()
        self.serial_slave_id_spin.setRange(1, 255)
        self.serial_slave_id_spin.setValue(1)
        serial_layout.addWidget(self.serial_slave_id_spin)
        
        serial_layout.addStretch(2) # 末尾留多点空间
        
        self.params_stack.addWidget(serial_widget)
        
        # TCP参数页面
        tcp_widget = QWidget()
        tcp_layout = QHBoxLayout(tcp_widget)
        
        # IP地址
        tcp_layout.addWidget(QLabel("IP地址:"))
        self.ip_edit = QLineEdit("127.0.0.1")
        self.ip_edit.setMinimumWidth(120)
        tcp_layout.addWidget(self.ip_edit)
        
        tcp_layout.addStretch(1) # 分隔
        
        # 端口
        tcp_layout.addWidget(QLabel("端口:"))
        self.port_spin = QSpinBox()
        self.port_spin.setRange(1, 65535)
        self.port_spin.setValue(502)
        tcp_layout.addWidget(self.port_spin)
        
        tcp_layout.addStretch(1) # 分隔
        
        # 超时
        tcp_layout.addWidget(QLabel("超时(秒):"))
        self.timeout_spin = QDoubleSpinBox()
        self.timeout_spin.setRange(0.1, 10.0)
        self.timeout_spin.setValue(1.0)
        self.timeout_spin.setSingleStep(0.1)
        tcp_layout.addWidget(self.timeout_spin)
        
        tcp_layout.addStretch(1) # 分隔
        
        # 从站地址
        tcp_layout.addWidget(QLabel("从站地址:"))
        self.tcp_slave_id_spin = QSpinBox()
        self.tcp_slave_id_spin.setRange(1, 255)
        self.tcp_slave_id_spin.setValue(1)
        tcp_layout.addWidget(self.tcp_slave_id_spin)
        
        tcp_layout.addStretch(2) # 末尾留多点空间
        
        self.params_stack.addWidget(tcp_widget)
        
        # 添加堆叠小部件到主布局
        main_layout.addWidget(self.params_stack)
        
        # 连接按钮布局
        btn_layout = QHBoxLayout()
        
        # 连接按钮
        self.connect_btn = QPushButton("连接")
        self.connect_btn.clicked.connect(self.connect_plc)
        btn_layout.addWidget(self.connect_btn)
        
        # 断开按钮
        self.disconnect_btn = QPushButton("断开")
        self.disconnect_btn.clicked.connect(self.disconnect_plc)
        self.disconnect_btn.setEnabled(False)
        btn_layout.addWidget(self.disconnect_btn)
        
        btn_layout.addStretch(1)  # 添加弹性空间
        main_layout.addLayout(btn_layout)
        
        # 初始化UI状态
        # self.on_device_type_changed(0) # This call is moved to create_ui
        
        return group
    
    def refresh_serial_ports(self):
        """刷新可用的串口列表"""
        self.com_port_combo.clear()
        ports = serial.tools.list_ports.comports()
        if ports:
            for port in ports:
                self.com_port_combo.addItem(f"{port.device} - {port.description}", port.device)
            self.log_signal.emit(f"已检测到 {len(ports)} 个可用串口", "info")
        else:
            self.com_port_combo.addItem("未检测到串口")
            self.log_signal.emit("未检测到可用串口设备", "error")
    
    def on_device_type_changed(self, index):
        """设备类型改变时的处理"""
        device_text = self.device_type_combo.currentText()
        device_type = self.device_type_combo.currentData() or self.device_mapping.get(device_text)
        
        # 根据设备类型显示不同的参数设置页面
        if device_type and "rtu" in device_type:
            self.params_stack.setCurrentIndex(0)  # 显示串口页面
        else:
            self.params_stack.setCurrentIndex(1)  # 显示TCP页面
            
        # 更新元件类型下拉框
        self.update_element_types()
        
        # 显示设备详细信息
        self.show_device_info(device_type)
    
    def show_device_info(self, device_type):
        """显示设备详细信息"""
        try:
            if device_type:
                devices = get_supported_devices()
                if device_type in devices:
                    info = devices[device_type]
                    info_text = f"设备: {info['manufacturer']} | 型号: {info.get('model', 'N/A')} | 通信: {info['communication']}"
                    self.log_signal.emit(info_text, "info")
        except Exception as e:
            self.log_signal.emit(f"获取设备信息失败: {str(e)}", "error")

    def create_device_status_area(self):
        """创建设备状态显示区域"""
        # 在状态栏中添加设备状态标签
        self.device_status_label = QLabel("设备状态: 未连接")
        self.device_status_label.setStyleSheet("QLabel { padding: 2px 8px; }")
        self.statusBar().addPermanentWidget(self.device_status_label)
        
        # 设备信息标签
        self.device_info_label = QLabel("")
        self.device_info_label.setStyleSheet("QLabel { padding: 2px 8px; color: #666; }")
        self.statusBar().addPermanentWidget(self.device_info_label)

    def update_device_status_display(self, device_info=None):
        """更新设备状态显示"""
        try:
            if self.connected and device_info:
                status_text = f"设备状态: 已连接"
                self.device_status_label.setText(status_text)
                self.device_status_label.setStyleSheet("QLabel { padding: 2px 8px; color: green; }")
                
                info_text = f"{device_info.get('manufacturer', '')} {device_info.get('model', '')} | {device_info.get('communication', '')}"
                self.device_info_label.setText(info_text)
            else:
                self.device_status_label.setText("设备状态: 未连接")
                self.device_status_label.setStyleSheet("QLabel { padding: 2px 8px; color: red; }")
                self.device_info_label.setText("")
        except Exception as e:
            self.log_signal.emit(f"更新状态显示失败: {str(e)}", "error")

    def create_menubar(self):
        """创建菜单栏"""
        menubar = self.menuBar()
        
        # 设备菜单
        device_menu = menubar.addMenu('设备(&D)')
        
        # 显示支持的设备
        show_devices_action = device_menu.addAction('支持的设备列表(&L)')
        show_devices_action.triggered.connect(self.show_supported_devices_dialog)
        
        device_menu.addSeparator()
        
        # 刷新设备列表
        refresh_devices_action = device_menu.addAction('刷新设备列表(&R)')
        refresh_devices_action.triggered.connect(self.refresh_device_list)

    def show_supported_devices_dialog(self):
        """显示支持的设备列表对话框"""
        try:
            devices = get_supported_devices()
            
            dialog_text = "支持的PLC设备类型：\n\n"
            for device_type, info in devices.items():
                dialog_text += f"• {info['manufacturer']} {info.get('model', '')}\n"
                dialog_text += f"  通信方式: {info['communication']}\n"
                dialog_text += f"  说明: {info.get('description', 'N/A')}\n\n"
            
            QMessageBox.information(self, "支持的设备", dialog_text)
        except Exception as e:
            QMessageBox.warning(self, "错误", f"获取设备列表失败: {str(e)}")

    def refresh_device_list(self):
        """刷新设备列表"""
        try:
            # 清空当前设备列表
            self.device_type_combo.clear()
            # 重新加载
            self.load_supported_devices()
            self.log_signal.emit("设备列表已刷新", "success")
        except Exception as e:
            self.log_signal.emit(f"刷新设备列表失败: {str(e)}", "error")

    def create_read_write_tab(self):
        """创建读写操作标签页"""
        widget = QWidget()
        layout = QVBoxLayout(widget)

        # 基本控制区域
        control_group = QGroupBox("元件访问")
        self.control_layout = QGridLayout(control_group)

        # 创建一个堆叠小部件来处理不同的UI布局
        self.access_stack = QStackedWidget()

        # --- 默认布局 (三菱) ---
        self.default_access_widget = QWidget()
        default_layout = QGridLayout(self.default_access_widget)
        default_layout.addWidget(QLabel("元件类型:"), 0, 0)
        self.element_type_combo = QComboBox()
        self.element_type_combo.currentIndexChanged.connect(self.on_element_type_changed)
        default_layout.addWidget(self.element_type_combo, 0, 1)
        
        default_layout.addWidget(QLabel("起始地址:"), 1, 0)
        self.start_address_spin = QSpinBox()
        self.start_address_spin.setRange(0, 7999)  # 限制为有效的D寄存器范围 (D0-D7999)
        self.start_address_spin.setValue(200)
        default_layout.addWidget(self.start_address_spin, 1, 1)
        self.access_stack.addWidget(self.default_access_widget)

        # --- 西门子布局 ---
        self.siemens_access_widget = QWidget()
        siemens_layout = QGridLayout(self.siemens_access_widget)
        siemens_layout.addWidget(QLabel("Modbus地址:"), 0, 0)
        self.modbus_address_edit = QLineEdit()
        self.modbus_address_edit.setPlaceholderText("40001-49999(保持寄存器), 10001-19999(输入), 1-9999(线圈)")
        siemens_layout.addWidget(self.modbus_address_edit, 0, 1)
        self.access_stack.addWidget(self.siemens_access_widget)

        self.control_layout.addWidget(self.access_stack, 0, 0, 1, 2)

        # 数量
        self.control_layout.addWidget(QLabel("读取数量:"), 1, 0)
        self.count_spin = QSpinBox()
        self.count_spin.setRange(1, 100)
        self.count_spin.setValue(10)
        self.control_layout.addWidget(self.count_spin, 1, 1)

        # 读取按钮
        self.read_btn = QPushButton("读取")
        self.read_btn.clicked.connect(self.read_from_plc)
        self.read_btn.setEnabled(False)
        self.control_layout.addWidget(self.read_btn, 2, 0, 1, 2)
        
        layout.addWidget(control_group)

        # 写入操作区域
        self.write_group = QGroupBox("寄存器写入")
        write_layout = QGridLayout(self.write_group)
        
        # 值类型
        write_layout.addWidget(QLabel("值类型:"), 0, 0)
        self.value_type_combo = QComboBox()
        self.value_type_combo.addItems(["Int", "Float", "Hex"])
        self.value_type_combo.currentIndexChanged.connect(self.on_value_type_changed)
        write_layout.addWidget(self.value_type_combo, 0, 1)
        
        # 值
        write_layout.addWidget(QLabel("寄存器值:"), 1, 0)
        self.register_value_edit = QLineEdit()
        self.register_value_edit.setPlaceholderText("单个值或逗号分隔的多个值")
        write_layout.addWidget(self.register_value_edit, 1, 1)
        
        # 写入按钮
        self.write_btn = QPushButton("写入")
        self.write_btn.clicked.connect(self.write_to_plc)
        self.write_btn.setEnabled(False)
        write_layout.addWidget(self.write_btn, 2, 0, 1, 2)
        
        layout.addWidget(self.write_group)
        
        # 位操作区域
        self.bit_group = QGroupBox("位状态写入")
        bit_layout = QHBoxLayout(self.bit_group)
        
        self.bit_state_check = QCheckBox("开/关")
        bit_layout.addWidget(self.bit_state_check)
        
        self.bit_write_btn = QPushButton("写入位状态")
        self.bit_write_btn.clicked.connect(self.write_bit_to_plc)
        self.bit_write_btn.setEnabled(False)
        bit_layout.addWidget(self.bit_write_btn)
        
        self.bit_group.setVisible(False)  # 默认隐藏
        layout.addWidget(self.bit_group)
        
        # 监控设置
        monitor_group = QGroupBox("实时监控")
        monitor_layout = QHBoxLayout(monitor_group)
        
        self.monitor_check = QCheckBox("启用实时监控")
        self.monitor_check.toggled.connect(self.toggle_monitor)
        monitor_layout.addWidget(self.monitor_check)
        
        monitor_layout.addWidget(QLabel("更新间隔(秒):"))
        self.monitor_interval_spin = QDoubleSpinBox()
        self.monitor_interval_spin.setRange(0.1, 10.0)
        self.monitor_interval_spin.setSingleStep(0.1)
        self.monitor_interval_spin.setValue(1.0)
        monitor_layout.addWidget(self.monitor_interval_spin)
        
        layout.addWidget(monitor_group)
        
        layout.addStretch(1)  # 添加弹性空间
        
        return widget
    
    # def create_monitor_tab(self):
    #     """创建批量监控标签页"""
    #     # 这是一个示例标签页，可以根据需要扩展
    #     widget = QWidget()
    #     layout = QVBoxLayout(widget)
        
    #     # 添加"开发中"标签
    #     layout.addWidget(QLabel("该功能正在开发中..."))
        
    #     return widget
    
    def connect_plc(self):
        """连接到PLC"""
        try:
            device_text = self.device_type_combo.currentText()
            self.device_type = self.device_type_combo.currentData() or self.device_mapping.get(device_text)
            
            if not self.device_type:
                QMessageBox.critical(self, "错误", f"未知的设备类型: {device_text}")
                return
            
            # 根据设备类型构建参数
            params = {}
            conn_info = ""

            if "rtu" in self.device_type:
                # 串口设备
                port_data = self.com_port_combo.currentData()
                if not port_data:
                    selected_text = self.com_port_combo.currentText()
                    if "未检测到串口" in selected_text:
                        QMessageBox.warning(self, "连接错误", "未检测到可用串口设备")
                        return
                    port = selected_text.split(" - ")[0]
                else:
                    port = port_data
                
                params['port'] = port
                params['baudrate'] = int(self.baudrate_combo.currentText())
                params['parity'] = self.parity_combo.currentText()[0] # 'N', 'E', 'O'
                params['slave_id'] = self.serial_slave_id_spin.value()
                conn_info = f"端口:{params['port']}, 波特率:{params['baudrate']}bps"

            else:
                # TCP设备
                params['host'] = self.ip_edit.text()
                params['port'] = self.port_spin.value()
                params['timeout'] = self.timeout_spin.value()
                params['slave_id'] = self.tcp_slave_id_spin.value()
                conn_info = f"IP:{params['host']}, 端口:{params['port']}"

            # 验证参数
            is_valid, error_msg = validate_client_parameters(self.device_type, **params)
            if not is_valid:
                QMessageBox.warning(self, "参数错误", error_msg)
                return

            # 使用工厂函数创建客户端
            self.plc = get_plc_client(self.device_type, **params)
            
            if not self.plc:
                QMessageBox.critical(self, "错误", f"不支持的设备类型: {self.device_type}")
                return

            # 尝试连接
            if self.plc.connect():
                self.connected = True
                self.update_ui_elements()
                self.log_signal.emit(f"已连接到 {device_text} ({conn_info})", "success")
                self.statusBar().showMessage(f"已连接: {device_text}")
                
                # 自动进行连接测试（如果设备支持）
                if hasattr(self.plc, 'test_connection'):
                    self.log_signal.emit("正在测试设备连接...", "info")
                    if self.plc.test_connection():
                        self.log_signal.emit("设备连接测试成功", "success")
                    else:
                        self.log_signal.emit("设备连接测试失败，可能需要检查配置", "error")
                
                # 显示设备信息
                if hasattr(self.plc, 'get_device_info'):
                    device_info = self.plc.get_device_info()
                    info_msg = f"已连接设备: {device_info.get('manufacturer', 'Unknown')} {device_info.get('model', '')}"
                    self.log_signal.emit(info_msg, "success")
                    
                    # 更新状态栏
                    self.update_device_status_display(device_info)
            else:
                QMessageBox.critical(self, "连接错误", "无法连接到PLC，请检查通信参数")
                self.log_signal.emit("连接失败", "error")
        except Exception as e:
            QMessageBox.critical(self, "连接错误", f"发生异常: {str(e)}\n{traceback.format_exc()}")
            self.log_signal.emit(f"连接异常: {str(e)}", "error")
    
    def disconnect_plc(self):
        """断开PLC连接"""
        # 停止监控
        if self.monitor_timer.isActive():
            self.monitor_timer.stop()
            self.monitor_check.setChecked(False)
        
        # 断开连接
        if self.plc:
            self.plc.close()
            self.connected = False
            self.update_ui_elements()
            self.update_device_status_display()  # 更新状态显示
            self.log_signal.emit("已断开连接", "info")
            self.statusBar().showMessage("未连接")
    
    def update_ui_elements(self):
        """根据连接状态更新UI元素"""
        # 连接按钮
        self.connect_btn.setEnabled(not self.connected)
        self.disconnect_btn.setEnabled(self.connected)
        
        # 操作按钮
        self.read_btn.setEnabled(self.connected)
        self.write_btn.setEnabled(self.connected)
        self.bit_write_btn.setEnabled(self.connected)
        
        # 连接类型选择器
        self.device_type_combo.setEnabled(not self.connected)

    def update_element_types(self):
        """根据设备类型更新元件类型下拉框和UI布局"""
        device_text = self.device_type_combo.currentText()
        device_type = self.device_type_combo.currentData() or self.device_mapping.get(device_text)
        self.element_type_combo.clear()

        if device_type and "mitsubishi" in device_type:
            self.access_stack.setCurrentWidget(self.default_access_widget)
            self.element_type_combo.addItems(["D", "D8", "R", "M", "X", "Y", "S"])
            # 默认显示寄存器写入，隐藏位写入
            self.write_group.setVisible(True)
            self.bit_group.setVisible(False)
        elif device_type and "siemens" in device_type:
            self.access_stack.setCurrentWidget(self.siemens_access_widget)
            # 对于西门子，我们将根据地址动态判断，所以不需要element_type_combo
            # 并且根据地址范围决定显示哪个写入框
            self.modbus_address_edit.clear() # 清空地址
            self.modbus_address_edit.textChanged.connect(self.on_siemens_address_changed)
            self.on_siemens_address_changed("") # 初始设置
        else:
            # 未知或新厂商设备，使用通用界面
            self.access_stack.setCurrentWidget(self.default_access_widget)
            self.element_type_combo.addItems(["通用寄存器", "通用线圈"])
            self.write_group.setVisible(True)
            self.bit_group.setVisible(False)
            self.log_signal.emit(f"使用通用界面: {device_type}", "info")

    def on_siemens_address_changed(self, address_str):
        """当西门子地址变化时，更新UI"""
        try:
            if not address_str:
                self.write_group.setVisible(True)
                self.bit_group.setVisible(False)
                return
                
            address = int(address_str)
            if 40001 <= address <= 49999: # Holding Registers
                self.write_group.setVisible(True)
                self.bit_group.setVisible(False)
            elif 1 <= address <= 9999 or 10001 <= address <= 19999: # Coils or Discrete Inputs
                self.write_group.setVisible(False)
                self.bit_group.setVisible(True)
                # I区是只读的
                is_readonly = 10001 <= address <= 19999
                self.bit_write_btn.setEnabled(self.connected and not is_readonly)
            else:
                self.write_group.setVisible(True) # 默认
                self.bit_group.setVisible(False)
        except ValueError:
            self.write_group.setVisible(True) # 输入非数字时默认
            self.bit_group.setVisible(False)

    def on_element_type_changed(self):
        """元件类型改变时的处理"""
        element_type = self.element_type_combo.currentText()
        
        # 根据元件类型设置默认地址
        if element_type.startswith("D"): # D, D8
            self.start_address_spin.setValue(200)
        elif element_type.startswith("V"): # V
            self.start_address_spin.setValue(0)
        elif element_type in ["R", "M", "X", "Y", "S", "I (Input)", "Q (Coil)"]:
            self.start_address_spin.setValue(0)
        
        # 显示/隐藏相应的操作区域
        is_bit_op = element_type in ["M", "X", "Y", "S", "I (Input)", "Q (Coil)"]
        self.write_group.setVisible(not is_bit_op)
        self.bit_group.setVisible(is_bit_op)

        if is_bit_op:
            # X和I是只读输入
            is_readonly = element_type in ["X", "I (Input)"]
            self.bit_write_btn.setEnabled(self.connected and not is_readonly)
        else: # 寄存器操作
            self.value_type_combo.clear()
            if element_type in ["D", "R", "V (Holding Reg)"]:
                self.value_type_combo.addItems(["Int", "Float", "Hex"])
            else:  # D8
                self.value_type_combo.addItems(["Int", "Hex"])

    def on_value_type_changed(self):
        """值类型改变时的处理"""
        value_type = self.value_type_combo.currentText()
        
        # 浮点数需要2个寄存器
        if value_type == "Float":
            self.count_spin.setValue(2)
            self.count_spin.setEnabled(False)
        else:
            self.count_spin.setEnabled(True)
    
    def read_from_plc(self):
        """从PLC读取数据"""
        if not self.connected:
            return
            
        try:
            device_text = self.device_type_combo.currentText()
            result = None
            count = self.count_spin.value()
            
            if "西门子" in device_text:
                address_str = self.modbus_address_edit.text()
                if not address_str:
                    QMessageBox.warning(self, "地址错误", "请输入Modbus地址")
                    return
                
                modbus_address = int(address_str)
                
                element_type = ""
                log_element_name = ""
                start_address = 0

                if 40001 <= modbus_address <= 49999:
                    element_type = "V (Holding Reg)"
                    log_element_name = "V"
                    start_address = modbus_address - 40001
                    result = self.plc.read_v_registers(start_address, count)
                elif 10001 <= modbus_address <= 19999:
                    element_type = "I (Input)"
                    log_element_name = "I"
                    start_address = modbus_address - 10001
                    result = self.plc.read_i_inputs(start_address, count)
                elif 1 <= modbus_address <= 9999:
                    element_type = "Q (Coil)"
                    log_element_name = "Q"
                    start_address = modbus_address - 1
                    result = self.plc.read_q_coils(start_address, count)
                else:
                    QMessageBox.warning(self, "地址错误", f"不支持的Modbus地址: {modbus_address}")
                    return

                # 显示结果
                if result is not None:
                    if "Coil" in element_type or "Input" in element_type:
                        self.display_coil_result(element_type, result, start_address)
                    else:
                        self.display_register_result(element_type, result, start_address)
                    self.log_signal.emit(f"读取 {log_element_name}{start_address}-{start_address+count-1} 成功", "success")
                else:
                    self.log_signal.emit(f"读取 {log_element_name}{start_address} 失败", "error")
                return

            # --- 以下是三菱的处理逻辑 ---
            element_type = self.element_type_combo.currentText()
            start_address = self.start_address_spin.value()
            
            # 根据设备和元件类型调用不同的读取方法
            if self.device_type.startswith("mitsubishi"):
                if element_type == "D" or element_type == "D8":
                    result = self.plc.read_d_registers(start_address, count)
                elif element_type == "R":
                    result = self.plc.read_r_registers(start_address, count)
                elif element_type == "M":
                    result = self.plc.read_m_coils(start_address, count)
                elif element_type == "X":
                    result = self.plc.read_x_coils(start_address, count)
                elif element_type == "Y":
                    result = self.plc.read_y_coils(start_address, count)
                elif element_type == "S":
                    result = self.plc.read_s_coils(start_address, count)
            
            elif self.device_type.startswith("siemens"):
                if element_type == "V (Holding Reg)":
                    result = self.plc.read_v_registers(start_address, count)
                elif element_type == "I (Input)":
                    result = self.plc.read_i_inputs(start_address, count)
                elif element_type == "Q (Coil)":
                    result = self.plc.read_q_coils(start_address, count)
            
            # 显示结果
            if "Coil" in element_type or "Input" in element_type or element_type in ["M", "X", "Y", "S"]:
                self.display_coil_result(element_type, result, start_address)
            else:
                self.display_register_result(element_type, result, start_address)

            log_element_name = element_type.split(" ")[0] # "V (Holding Reg)" -> "V"
            self.log_signal.emit(f"读取 {log_element_name}{start_address}-{start_address+count-1} 成功", "success")
        except Exception as e:
            QMessageBox.warning(self, "读取错误", f"读取出错: {str(e)}")
            self.log_signal.emit(f"读取异常: {str(e)}", "error")
    
    def write_to_plc(self):
        """向PLC写入数据"""
        if not self.connected:
            return
            
        try:
            device_text = self.device_type_combo.currentText()
            
            if "西门子" in device_text:
                address_str = self.modbus_address_edit.text()
                if not address_str:
                    QMessageBox.warning(self, "地址错误", "请输入Modbus地址")
                    return
                modbus_address = int(address_str)
                
                if not (40001 <= modbus_address <= 49999):
                    QMessageBox.warning(self, "操作错误", "当前地址不支持寄存器写入")
                    return
                
                start_address = modbus_address - 40001
                value_type = self.value_type_combo.currentText()
                value_str = self.register_value_edit.text().strip()

                # 处理不同类型的值
                values = []
                if value_type == "Int":
                    values = [int(x.strip()) for x in value_str.split(",") if x.strip()] if "," in value_str else [int(value_str)]
                elif value_type == "Hex":
                     clean_str = value_str.replace("0x", "")
                     values = [int(x.strip(), 16) for x in clean_str.split(",") if x.strip()] if "," in clean_str else [int(clean_str, 16)]
                
                success = self.plc.write_v_registers(start_address, values)
                if success:
                    self.log_signal.emit(f"写入值 {values} 到 V{start_address} 成功", "success")
                    self.read_from_plc()
                else:
                    self.log_signal.emit(f"写入值 {values} 到 V{start_address} 失败", "error")
                return

            element_type = self.element_type_combo.currentText()
            start_address = self.start_address_spin.value()
            value_type = self.value_type_combo.currentText()
            value_str = self.register_value_edit.text().strip()
            
            if not value_str:
                QMessageBox.warning(self, "输入错误", "请输入要写入的值")
                return
                
            # 处理不同类型的值
            if value_type == "Float":
                # 浮点数需要特殊处理
                value = float(value_str)
                success = False
                if self.device_type.startswith("mitsubishi") and element_type in ["D", "R"]:
                    success = self.plc.write_float(start_address, value)
                # elif self.device_type.startswith("siemens") and element_type == "V (Holding Reg)":
                #     # 西门子浮点数写入需要额外实现
                #     QMessageBox.warning(self, "功能待定", "西门子浮点数写入功能暂未实现")
                #     return
                else:
                    QMessageBox.warning(self, "类型错误", f"浮点数不能写入 {element_type} 寄存器")
                    return

                if success:
                    self.log_signal.emit(f"写入浮点数 {value} 到 {element_type} {start_address} 成功", "success")
                else:
                    self.log_signal.emit(f"写入浮点数 {value} 到 {element_type} {start_address} 失败", "error")
                return

            # 处理Int和Hex
            values = []
            if value_type == "Int":
                if "," in value_str:
                    # 多个整数值
                    values = [int(x.strip()) for x in value_str.split(",")]
                else:
                    # 单个整数值
                    values = [int(value_str)]
            elif value_type == "Hex":
                if "," in value_str:
                    # 多个十六进制值
                    values = []
                    for x in value_str.split(","):
                        x = x.strip()
                        if x.startswith("0x"):
                            values.append(int(x, 16))
                        else:
                            values.append(int(x, 16))
                else:
                    # 单个十六进制值
                    if value_str.startswith("0x"):
                        values = [int(value_str, 16)]
                    else:
                        values = [int(value_str, 16)]
            
            # 执行写入操作
            success = False
            if self.device_type.startswith("mitsubishi"):
                if element_type in ["D", "D8"]:
                    success = self.plc.write_d_registers(start_address, values)
                elif element_type == "R":
                    success = self.plc.write_r_registers(start_address, values)
            elif self.device_type.startswith("siemens"):
                if element_type == "V (Holding Reg)":
                    success = self.plc.write_v_registers(start_address, values)

            log_element_name = element_type.split(" ")[0]
            if success:
                self.log_signal.emit(f"写入值 {values} 到 {log_element_name}{start_address} 成功", "success")
                # 写入成功后立即读取以显示结果
                self.read_from_plc()
            else:
                self.log_signal.emit(f"写入值 {values} 到 {log_element_name}{start_address} 失败", "error")
        
        except ValueError as e:
            QMessageBox.warning(self, "输入错误", f"值格式错误: {str(e)}")
            self.log_signal.emit(f"值格式错误: {str(e)}", "error")
        except Exception as e:
            QMessageBox.warning(self, "写入错误", f"写入出错: {str(e)}")
            self.log_signal.emit(f"写入异常: {str(e)}", "error")
    
    def write_bit_to_plc(self):
        """写入位状态"""
        if not self.connected:
            return
            
        try:
            device_text = self.device_type_combo.currentText()
            state = self.bit_state_check.isChecked()

            if "西门子" in device_text:
                address_str = self.modbus_address_edit.text()
                if not address_str:
                    QMessageBox.warning(self, "地址错误", "请输入Modbus地址")
                    return
                modbus_address = int(address_str)
                
                if 1 <= modbus_address <= 9999: # Q区
                    start_address = modbus_address - 1
                    success = self.plc.write_q_coils(start_address, [state])
                    if success:
                        self.log_signal.emit(f"写入位状态 {'ON' if state else 'OFF'} 到 Q{start_address} 成功", "success")
                        self.read_from_plc()
                    else:
                        self.log_signal.emit(f"写入位状态 {'ON' if state else 'OFF'} 到 Q{start_address} 失败", "error")
                elif 10001 <= modbus_address <= 19999: # I区
                     QMessageBox.warning(self, "操作错误", "I区是输入点，不能写入")
                else:
                    QMessageBox.warning(self, "操作错误", "当前地址不支持位写入")
                return

            element_type = self.element_type_combo.currentText()
            start_address = self.start_address_spin.value()
            
            # 只读输入点检查
            if element_type in ["X", "I (Input)"]:
                QMessageBox.warning(self, "操作错误", f"{element_type}是输入点，不能写入")
                return
            
            # M0和M1互斥逻辑 (仅三菱)
            if self.device_type.startswith("mitsubishi") and element_type == "M" and state:
                if start_address == 0:
                    # 如果设置M0为1，则需要将M1设为0
                    self.plc.write_m_coils(1, [False])
                    self.log_signal.emit(f"互斥控制: 设置 M1 为 OFF", "info")
                elif start_address == 1:
                    # 如果设置M1为1，则需要将M0设为0
                    self.plc.write_m_coils(0, [False])
                    self.log_signal.emit(f"互斥控制: 设置 M0 为 OFF", "info")
                
            # 执行写入操作
            success = False
            if self.device_type.startswith("mitsubishi"):
                if element_type == "M":
                    success = self.plc.write_m_coils(start_address, [state])
                elif element_type == "Y":
                    success = self.plc.write_y_coils(start_address, [state])
                elif element_type == "S":
                    success = self.plc.write_s_coils(start_address, [state])
            elif self.device_type.startswith("siemens"):
                if element_type == "Q (Coil)":
                    success = self.plc.write_q_coils(start_address, [state])

            log_element_name = element_type.split(" ")[0]
            if success:
                self.log_signal.emit(f"写入位状态 {'ON' if state else 'OFF'} 到 {log_element_name}{start_address} 成功", "success")
                # 写入成功后立即读取以显示结果
                self.read_from_plc()
            else:
                self.log_signal.emit(f"写入位状态 {'ON' if state else 'OFF'} 到 {log_element_name}{start_address} 失败", "error")
        
        except Exception as e:
            QMessageBox.warning(self, "写入错误", f"写入出错: {str(e)}")
            self.log_signal.emit(f"写入异常: {str(e)}", "error")
    
    def toggle_monitor(self, checked):
        """切换实时监控状态"""
        if checked:
            if not self.connected:
                QMessageBox.warning(self, "监控错误", "请先连接PLC")
                self.monitor_check.setChecked(False)
                return
            
            # 检查地址是否已设置
            device_text = self.device_type_combo.currentText()
            if "西门子" in device_text:
                if not self.modbus_address_edit.text().strip():
                    QMessageBox.warning(self, "监控错误", "请先输入要监控的Modbus地址")
                    self.monitor_check.setChecked(False)
                    return
            # 三菱设备总是有默认地址，所以不需要检查
                
            # 启动监控
            interval = int(self.monitor_interval_spin.value() * 1000)  # 毫秒
            self.monitor_timer.start(interval)
            self.log_signal.emit(f"启动实时监控 (间隔: {self.monitor_interval_spin.value()}秒)", "info")
        else:
            # 停止监控
            self.monitor_timer.stop()
            self.log_signal.emit("停止实时监控", "info")
    
    def poll_plc(self):
        """定时轮询PLC数据"""
        if self.connected:
            self.read_from_plc()
    
    def display_register_result(self, prefix, registers, start_address):
        """显示寄存器读取结果"""
        if registers is None:
            self.display_signal.emit("读取失败")
            return
            
        text = ""
        
        # 判断是否需要解析为浮点数
        element_type = self.element_type_combo.currentText()
        value_type = self.value_type_combo.currentText() if hasattr(self, 'value_type_combo') else ""
        
        if element_type in ["D", "R", "V (Holding Reg)"] and value_type == "Float" and len(registers) >= 2:
            try:
                # 浮点数读取对于不同PLC可能需要不同实现，这里暂时用三菱的
                # 注意：西门子的浮点数需要特定的字节序/字序设置，这里未处理
                if self.device_type.startswith("mitsubishi"):
                    float_val = self.plc.read_float(start_address)
                    prefix_name = prefix.split(" ")[0]
                    text += f"{prefix_name}{start_address}-{prefix_name}{start_address+1}: "
                    text += f"{float_val} (浮点数值)\n\n"
                else:
                    text += "暂不支持西门子浮点数直接解析。\n"
            except Exception as e:
                self.log_signal.emit(f"浮点数解析错误: {str(e)}", "error")
            return self.display_signal.emit(text) 
        
        # 只显示请求的寄存器数量
        count = self.count_spin.value()  # 获取用户设置的数量
        prefix_name = prefix.split(" ")[0]
        for i, reg in enumerate(registers[:count]):  # 只处理请求数量的寄存器
            addr = start_address + i
            text += f"{prefix_name}{addr}: {reg} (0x{reg:04X})\n"
        
        self.display_signal.emit(text)
    
    def display_coil_result(self, prefix, coils, start_address):
        """显示线圈读取结果"""
        if coils is None:
            self.display_signal.emit("读取失败")
            return
            
        text = ""
        
        # 只显示请求的线圈数量
        count = self.count_spin.value()  # 获取用户设置的数量
        prefix_name = prefix.split(" ")[0]
        for i, state in enumerate(coils[:count]):  # 只处理请求数量的线圈
            addr = start_address + i
            text += f"{prefix_name}{addr}: {'ON' if state else 'OFF'}\n"
        
        self.display_signal.emit(text)
    
    @pyqtSlot(str)
    def update_display(self, text):
        """更新显示区域"""
        self.result_text.clear()
        self.result_text.setText(text)
    
    @pyqtSlot(str, str)
    def update_log(self, message, msg_type="info"):
        """更新日志区域"""
        # 格式化日志条目
        time_str = time.strftime("%H:%M:%S")
        log_entry = f"[{time_str}] {message}"
        
        # 设置不同类型消息的颜色
        cursor = self.log_text.textCursor()
        format_normal = QTextCharFormat()
        
        if msg_type == "error":
            format_normal.setForeground(QColor("red"))
        elif msg_type == "success":
            format_normal.setForeground(QColor("green"))
        else:  # info
            format_normal.setForeground(QColor("black"))
        
        # 移动光标到文档开头
        cursor.movePosition(QTextCursor.Start)
        
        # 插入新的日志条目
        cursor.insertText(log_entry + "\n", format_normal)

    def closeEvent(self, event):
        """窗口关闭事件处理"""
        if self.connected:
            self.disconnect_plc()
        
        event.accept()

if __name__ == "__main__":
    # 确保在Windows上高DPI缩放正常
    if sys.platform.startswith('win'):
        try:
            from PyQt5.Qt import QCoreApplication
            QCoreApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
        except ImportError:
            pass

    try:
        app = QApplication(sys.argv)
        app.setStyle('Fusion')
        
        window = PLCControlUI()
        window.show()
        
        # 列出所有可用的串口
        ports = serial.tools.list_ports.comports()

        # 打印每个端口的信息
        for port in ports:
            print(f"端口: {port.device}, 描述: {port.description}")
        
        sys.exit(app.exec_())
    except Exception as e:
        print(f"程序启动过程中发生异常: {e}")
        traceback.print_exc() 